게시판 만들기
✒️ 2025-05-26 11:07 내용 수정
실습 목표
- 게시글을 작성, 조회, 삭제하고, 댓글을 작성하는 사이트를 만든다.
- DB와 연결하여 게시글 내용을 저장, 조회, 삭제한다.
실습 흐름
- 데이터베이스에 테이블 및 시퀀스, 필요 시 샘플 데이터 추가
- 데이터베이스에 연결 : context.xml 파일, 라이브러리(JDBC)
- 테이블의 정보를 저장할 DTO 클래스 생성
- 데이터베이스에서 조회, 추가, 삭제를 수행하는 DAO 클래스 생성
- 게시글과 댓글을 등록, 삭제, 조회할 JSP를 생성
- 페이징 처리를 담당할 클래스 생성
- DAO 클래스 객체를 생성하고, JSP와 연결할 Servlet 생성
- Servlet에서 코드 실행 시 결과 확인
DB에 테이블 추가
--시퀀스
CREATE SEQUENCE SEQ_BOARD_IDX;
--테이블
CREATE TABLE BOARD(
IDX NUMBER(3) PRIMARY KEY, --번호
NAME VARCHAR2(100) NOT NULL, --작성자
SUBJECT VARCHAR2(255) NOT NULL, --게시글 이름
CONTENT CLOB, --게시글 내용
PWD VARCHAR2(100), --비밀번호
IP VARCHAR2(100), --IP
REGDATE DATE, --작성일
READHIT NUMBER(3) DEFAULT 0, --조회수
REF INT, --기준글번호(댓글의 메인글 번호)
STEP INT, --댓글순서
DEPTH INT, --대댓글
DEL_INFO NUMBER(2) --글 삭제여부
);
-- 샘플 데이터 추가
INSERT INTO BOARD VALUES(
SEQ_BOARD_IDX.nextVal,
'에이스',
'게시판 첫 글',
'게시판 첫 번째 글은 내가 작성했다',
'1234',
'192.0.0.2',
SYSDATE,
0,
SEQ_BOARD_IDX.currval,
0,
0,
0
);
-- 댓글 샘플 데이터
INSERT INTO BOARD VALUES(
SEQ_BOARD_IDX.nextVal,
'브라보',
'오늘 날씨',
'오늘 날씨 비 오고 눈 내림',
'1234',
'192.0.0.2',
SYSDATE,
0,
1,
1,
1,
0
);
-- 댓글의 댓글 샘플 데이터
INSERT INTO BOARD VALUES(
SEQ_BOARD_IDX.nextVal,
'칼리',
'대댓글',
'대대대대댓글',
'1234',
'192.0.0.3',
SYSDATE,
0,
1,
2,
2,
0
);
- ref : 댓글을 작성할 때 기준이 되는 원본 글의 번호
- step : 수직적 댓글 순서. 일반 글은 0, 댓글은 1부터 시작하며, 가장 최신 댓글일수록, 댓글의 댓글이 아닐수록 숫자가 작다.
- 댓글이 추가될 때 가장 최근 댓글의 step이 1이 되어야 댓글을 최신 작성순으로 확인할 수 있다.
- 따라서 해당 글에 이미 존재하는 다른 댓글들에 step을 1씩 추가해야 한다.
- depth : 댓글 깊이(댓글의 댓글). 일반 댓글은 1이며, 댓글의 댓글이면 2가 된다.
DB 연결
- context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource
auth="Container"
name="jdbc/oracle_test"
type="javax.sql.DataSource"
driverClassName="oracle.jdbc.driver.OracleDriver"
factory="org.apache.commons.dbcp.BasicDataSourceFactory"
url="jdbc:oracle:thin:@localhost:1521:xe"
username="계정명" password="비밀번호"
maxActive="20" maxIdle="10" maxWait="1"/>
</Context>
- 라이브러리
- 라이브러리에 lomboc 추가하여 사용
| 파일 |
|---|
| commons-collections-3.2.1.jar |
| commons-dbcp-1.2.2.jar |
| commons-pool-1.4.jar |
| ojdbc8-23.3.0.23.09.jar |
| lomboc.jar |
| mybatis-3.1.1.jar |
- MybatisConnector 클래스
package service;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MybatisConnector {
static MybatisConnector single = null;
SqlSessionFactory factory = null;
public static MybatisConnector getInstance() {
if (single == null) {
single = new MybatisConnector();
}
return single;
}
public MybatisConnector() {
try {
// sqlMapConfig.xml 파일 읽어오기
Reader reader = Resources.getResourceAsReader("config/mybatis/sqlMapConfig.xml");
// SqlSessionFactoryBuilder로 SqlSessionFactory 만들기
factory = new SqlSessionFactoryBuilder().build(reader);
reader.close();
} catch (Exception e) {
}
}
// sqlMapConfig.xml의 정보를 담고 있는 factory 객체 반환
public SqlSessionFactory getsqlSessionFactory() {
return factory;
}
}
- SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 별칭을 주면 mapper에서 별칭과 대응되는 패키지와 클래스를 인식 -->
<typeAliases>
<typeAlias type="dto.BoardDTO" alias="board"/>
</typeAliases>
<environments default="">
<environment id="">
<transactionManager type="JDBC" />
<dataSource type="JNDI">
<property name="data_source"
value="java:comp/env/jdbc/oracle_test" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="config/mybatis/mapper/board.xml" />
</mappers>
</configuration>
Ajax
- HttpRequest.js
var xhr = null;
function createRequest() {
if (xhr != null) {
return;
}
if (window.ActiveXObject) {
xhr = new ActiveXObject("Microsoft.XMLHTTP"); // IE 환경
} else {
xhr = new XMLHttpRequest(); // 기타 브라우저 환경
}
}
function sendRequest(url, param, callback, method) {
// HttpRequest 생성
createRequest();
// 전송 타입 구분
var httpMethod = (method != 'POST' && method != 'post') ? 'GET' : 'POST';
// 파라미터 구분
var httpParam = (param == null || param == '') ? null : param;
// 접근 url
var httpURL = url;
// 요청 방식이 GET이고 전달할 파라미터가 있다면 새 url 경로 제작
if (httpMethod == 'GET' && httpParam != null) {
httpURL = httpURL+'?'+httParam;
}
// 서버로 보낼 Ajax 요청 형식
xhr.open(httpMethod, httpURL, true);
// requestHeader 설정 : Content-Type 지정
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
// 작업이 완료된 후 호출할 callback 메소드 지정
xhr.onreadystatechange = callback;
// Ajax 요청을 서버로 전달
xhr.send(httpMethod == 'POST' ? httpParam : null);
}
DTO와 DAO
- dto
package dto;
import lombok.Data;
@Data
public class BoardDTO {
private int idx;
private int readhit;
private int ref;
private int step;
private int depth;
private int del_info;
private String name;
private String subject;
private String content;
private String pwd;
private String ip;
private String regdate;
}
- dao
package dao;
import java.util.HashMap;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import dto.BoardDTO;
import service.MybatisConnector;
public class BoardDAO {
static BoardDAO single = null;
SqlSessionFactory factory;
public static BoardDAO getInstance() {
if (single == null) {
single = new BoardDAO();
}
return single;
}
public BoardDAO() {
factory = MybatisConnector.getInstance().getsqlSessionFactory();
}
// 게시글 조회하기
public List<BoardDTO> select(HashMap<String, Integer> map) {
SqlSession sqlSession = factory.openSession();
List<BoardDTO> list = sqlSession.selectList("b.board_list", map);
sqlSession.close();
return list;
}
// 전체 게시글 조회하기
public int getRowTotal() {
SqlSession sqlSession = factory.openSession();
int count = sqlSession.selectOne("b.board_count");
sqlSession.close();
return count;
}
// 게시글 추가하기
public int insert(BoardDTO dto) {
// DB에 commit을 위해 openSession(true)
SqlSession sqlSession = factory.openSession(true);
int res = sqlSession.insert("b.board_insert", dto);
sqlSession.close();
return res;
}
// 게시글 상세보기
public BoardDTO selectOne(int idx) {
SqlSession sqlSession = factory.openSession();
BoardDTO dto = sqlSession.selectOne("b.board_one", idx);
sqlSession.close();
return dto;
}
// 조회수 증가하기
public int update_readhit(int idx) {
SqlSession sqlSession = factory.openSession(true);
int res = sqlSession.update("b.board_update_readhit", idx);
sqlSession.close();
return res;
}
// 답글 추가를 위한 step = step + 1
public int update_step(BoardDTO dto) {
SqlSession sqlSession = factory.openSession(true);
int res = sqlSession.update("b.board_update_step", dto);
sqlSession.close();
return res;
}
// 답글 등록
public int reply(BoardDTO dto) {
SqlSession sqlSession = factory.openSession(true);
int res = sqlSession.insert("b.board_reply", dto);
sqlSession.close();
return res;
}
// 삭제된 것처럼 수정
public int del_update(BoardDTO dto) {
SqlSession sqlSession = factory.openSession(true);
int res = sqlSession.update("b.board_del", dto);
sqlSession.close();
return res;
}
}
mapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="b">
<!-- 전체 게시글 조회 -->
<select id="board_list" parameterType="java.util.HashMap" resultType="board">
SELECT * FROM (SELECT RANK() OVER(ORDER BY ref DESC, step) AS no, b.* FROM BOARD b)
WHERE no BETWEEN #{start} AND #{end}
</select>
<!-- 전체 게시글 조회 -->
<select id="board_count" resultType="int">
SELECT COUNT(*) FROM BOARD
</select>
<!-- 게시글 추가 -->
<insert id="board_insert" parameterType="board">
INSERT INTO BOARD VALUES(
SEQ_BOARD_IDX.nextVal,
#{name},
#{subject},
#{content},
#{pwd},
#{ip},
SYSDATE,
0,
SEQ_BOARD_IDX.currVal,
0,
0,
0
)
</insert>
<!-- 게시글 상세보기 -->
<select id="board_one" parameterType="int" resultType="board">
SELECT * FROM BOARD WHERE IDX = #{idx}
</select>
<!-- 조회수 증가 -->
<update id="board_update_readhit" parameterType="int">
UPDATE BOARD
SET READHIT = READHIT + 1
WHERE IDX = #{idx}
</update>
<!-- 답글 작성을 위한 step 증가 -->
<!-- 현재 글의 step보다 step이 큰 글들의 step 증가 -->
<update id="board_update_step" parameterType="board">
UPDATE BOARD
SET STEP = STEP + 1
WHERE REF = #{ref} AND STEP > #{step}
</update>
<!-- 답글 추가 -->
<insert id="board_reply" parameterType="board">
INSERT INTO BOARD VALUES(
SEQ_BOARD_IDX.nextVal,
#{name},
#{subject},
#{content},
#{pwd},
#{ip},
SYSDATE,
0,
#{ref},
#{step},
#{depth},
0
)
</insert>
<!-- 게시글이 삭제된 것처럼 처리 -->
<update id="board_del" parameterType="board">
UPDATE BOARD
SET SUBJECT = #{subject},
NAME = #{name},
DEL_INFO = -1
WHERE IDX = #{idx}
</update>
</mapper>
JSP
- 게시판 메인 화면 jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style type="text/css">
a{text-decoration:none;}
table{width:700px;
border-collapse:collapse;}
table h1{text-align:center;}
</style>
</head>
<body>
<table border="1" align="center">
<tr>
<td colspan="5"><h1>게시판</h1></td>
</tr>
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
<th>조회수</th>
</tr>
<c:forEach var="dto" items="${list}">
<tr>
<td align="center">${dto.idx}</td>
<td>
<!-- 댓글 indent -->
<c:forEach begin="1" end="${dto.depth}"> </c:forEach>
<c:if test="${dto.depth ne 0}">ㄴ</c:if>
<!-- 삭제되지 않은 글 일때만 클릭 가능 -->
<c:if test="${dto.del_info ne -1}">
<a href="view?idx=${dto.idx}&page=${param.page}">
<font color="black">${dto.subject}</font>
</a>
</c:if>
<!-- 삭제된 글은 클릭 불가 -->
<c:if test="${dto.del_info eq -1}">
<font color="gray">${dto.subject}</font>
</c:if>
</td>
<td>${dto.name}</td>
<td>${fn:split(dto.regdate,' ')[0]}</td>
<td>${dto.readhit}</td>
</tr>
</c:forEach>
<tr>
<!-- 페이지 넘어가는 하단 메뉴바 -->
<td colspan="5" align="center"> ${pageMenu} </td>
</tr>
<tr>
<td colspan="5" align="right">
<input type="button" value="글작성" onclick="location.href='insert_form.jsp'">
</td>
</tr>
</table>
</body>
</html>
- 게시글 추가하기 jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
function send() {
var f = document.f;
var subject = f.subject.value;
var name = f.name.value;
var content = f.content.value;
var pwd = f.pwd.value;
// 유효성 검사
if (subject == '') {
alert('제목을 입력하세요');
return;
}
if (name == '') {
alert('이름을 입력하세요');
return;
}
if (content == '') {
alert('내용을 한 글자 이상 입력하세요');
return;
}
if (pwd == '') {
alert('비밀번호를 입력하세요');
return;
}
f.submit();
}
</script>
</head>
<body>
<form name="f" method="POST" action="insert">
<table border="1" align="center">
<caption>::새 글 쓰기::</caption>
<tr>
<th>제목</th>
<td><input name="subject"></td>
</tr>
<tr>
<th>작성자</th>
<td><input name="name"></td>
</tr>
<tr>
<th>내용</th>
<td>
<textarea name="content" rows="10" cols="50" style="resize:none;"></textarea>
</td>
<tr>
<th>비밀번호</th>
<td><input name="pwd" type="password"></td>
</tr>
<tr>
<td colspan="2">
<input type="button" value="등록하기" onclick="send()">
<input type="button" value="목록으로 돌아가기" onclick="location.href='board_list'">
</td>
</tr>
</table>
</form>
</body>
</html>
- 답글 달기 jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
function send() {
var f = document.f;
var subject = f.subject.value;
var name = f.name.value;
var content = f.content.value;
var pwd = f.pwd.value;
// 유효성 검사
if (subject == '') {
alert('제목을 입력하세요');
return;
}
if (name == '') {
alert('이름을 입력하세요');
return;
}
if (content == '') {
alert('내용을 한 글자 이상 입력하세요');
return;
}
if (pwd == '') {
alert('비밀번호를 입력하세요');
return;
}
f.submit();
}
</script>
</head>
<body>
<form name="f" method="POST" action="reply">
<input type="hidden" name="idx" value="${param.idx}">
<input type="hidden" name="page" value="${param.page}">
<table border="1" align="center">
<caption>::댓글 쓰기::</caption>
<tr>
<th>제목</th>
<td><input name="subject"></td>
</tr>
<tr>
<th>작성자</th>
<td><input name="name"></td>
</tr>
<tr>
<th>내용</th>
<td>
<textarea name="content" rows="10" cols="50" style="resize:none;"></textarea>
</td>
<tr>
<th>비밀번호</th>
<td><input name="pwd" type="password"></td>
</tr>
<tr>
<td colspan="2">
<input type="button" value="등록하기" onclick="send()">
<input type="button" value="목록으로 돌아가기" onclick="location.href='board_list?page=${param.page}'">
</td>
</tr>
</table>
</form>
</body>
</html>
- 게시글 상세보기 jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="js/HttpRequest.js"></script>
<script type="text/javascript">
function reply() {
location.href = "reply_form.jsp?idx=${dto.idx}&page=${param.page}";
}
function del() {
if (!confirm('삭제하시겠습니까?')) {
return;
}
var pwd = "${dto.pwd}";
var c_pwd = document.getElementById("c_pwd").value;
if (c_pwd != pwd) {
alert('비밀번호가 일치하지 않습니다');
return;
}
var url = "del";
var param = "idx=${dto.idx}";
sendRequest(url, param, delCheck, "POST");
}
function delCheck() {
if(xhr.readyState == 4 && xhr.status == 200) {
var data = xhr.responseText;
var json = eval(data);
if(json[0].param == "yes") {
alert('게시글이 정상적으로 삭제되었습니다');
location.href = "board_list?page=${param.page}";
} else {
alert('게시글 삭제에 실패했습니다');
}
}
}
</script>
</head>
<body>
<table border="1" align="center">
<caption>:::게시글 상세보기:::</caption>
<tr>
<th>제목</th>
<td>${dto.subject}</td>
</tr>
<tr>
<th>작성자</th>
<td>${dto.name}</td>
</tr>
<tr>
<th>작성일</th>
<td>${dto.regdate}</td>
</tr>
<tr>
<th>내용</th>
<td width="500px" height="200px">
<pre>${dto.content}</pre>
</td>
</tr>
<tr>
<th>조회수</th>
<td>${dto.readhit}</td>
</tr>
<tr>
<th>비밀번호</th>
<td><input type="password" id="c_pwd"></td>
</tr>
<tr>
<td colspan="2">
<!-- 목록으로 돌아가기 -->
<input type="button" value="목록으로 돌아가기" onclick="location.href='board_list?page=${param.page}'">
<!-- 댓글의 댓글을 무한 생성 못하게 설정 -->
<c:if test="${dto.depth lt 1}">
<!-- 댓글 추가 -->
<input type="button" value="댓글 달기" onclick="reply();">
</c:if>
<!-- 글삭제 -->
<input type="button" value="글삭제" onclick="del();">
</td>
</tr>
</table>
</body>
</html>
Servlet
- 게시판 조회 Servlet
package action;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import dto.BoardDTO;
import util.Common;
import util.Paging;
@WebServlet("/board_list")
public class BoardListAction extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 글자가 안 깨지도록 설정
request.setCharacterEncoding("utf-8");
int nowPage = 1;
String page = request.getParameter("page");
if (page != null && !page.isEmpty()) {
// board_list --> null
// board_list?page= --> empty
nowPage = Integer.parseInt(page);
}
// 한 페이지에 표시될 게시물의 시작과 끝 번호 계산
// page = 1 --> 1 ~ 10
// page = 2 --> 11 ~ 20
int start = (nowPage - 1) * Common.Board.BLOCKLIST + 1;
int end = start + Common.Board.BLOCKLIST - 1;
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("start", start);
map.put("end", end);
// dao객체 만들기 -- DB에 여러 번 접근하기 위해
BoardDAO dao = BoardDAO.getInstance();
// 게시글 전체 목록 조회
List<BoardDTO> list = dao.select(map);
// 게시글 전체 수 가져오기
int rowTotal = dao.getRowTotal();
// 페이지 메뉴 생성
String pageMenu = Paging.getPaging("board_list", nowPage, rowTotal, Common.Board.BLOCKLIST, Common.Board.BLOCKPAGE);
// 새로 페이지 접근 시 조회수 증가를 위해 session의 show 제거
// session은 /board_view에서 생성됨
request.getSession().removeAttribute("show");
// 바인딩
request.setAttribute("list", list);
request.setAttribute("pageMenu", pageMenu);
// 포워딩
RequestDispatcher disp = request.getRequestDispatcher("board_list.jsp?page="+nowPage);
disp.forward(request, response);
}
}
- 게시판 추가 Servlet
package action;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import dto.BoardDTO;
@WebServlet("/insert")
public class BoardInsertAction extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 글이 깨지지 않게 인코딩 설정
request.setCharacterEncoding("utf-8");
String subject = request.getParameter("subject");
String name = request.getParameter("name");
String content = request.getParameter("content");
String pwd = request.getParameter("pwd");
String ip = request.getRemoteAddr();
BoardDTO dto = new BoardDTO();
dto.setSubject(subject);
dto.setName(name);
dto.setContent(content);
dto.setPwd(pwd);
dto.setIp(ip);
int res = BoardDAO.getInstance().insert(dto);
if (res > 0) {
response.sendRedirect("board_list");
}
}
}
- 게시판 답글 Servlet
package action;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import dto.BoardDTO;
@WebServlet("/reply")
public class BoardReplyAction extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 글이 깨지지 않게 인코딩 설정
request.setCharacterEncoding("utf-8");
// 페이지 설정 -- 답글 등록 후 같은 페이지에 남기
int nowPage = 1;
String page = request.getParameter("page");
if (page != null && !page.isEmpty()) {
nowPage = Integer.parseInt(page);
}
int idx = Integer.parseInt(request.getParameter("idx"));
String subject = request.getParameter("subject");
String name = request.getParameter("name");
String content = request.getParameter("content");
String pwd = request.getParameter("pwd");
String ip = request.getRemoteAddr();
// dao객체 생성해두기 -- DB에 2번 이상 접근하기 위해
BoardDAO dao = BoardDAO.getInstance();
// 같은 ref를 가진 데이터들 중 내가 추가하려는 step 값 이상인 글들을
// (step + 1) 처리해야 함 ==> 게시글이 최신 작성 순으로 정렬됨
// 기준글의 idx를 이용해서 댓글을 달고 싶은 게시글의 정보 가져오기
BoardDTO base_dto = dao.selectOne(idx);
// 현재 댓글의 step 이상인 댓글들을 모두 (step + 1) 처리
// 결과 반환은 다른 곳에서 호출 안해둠 --> 오류 발생 시 사용해볼 것
int res_step = dao.update_step(base_dto);
// 댓글 정보 저장 및 댓글 등록
BoardDTO dto = new BoardDTO();
dto.setSubject(subject);
dto.setName(name);
dto.setContent(content);
dto.setPwd(pwd);
dto.setIp(ip);
dto.setRef(base_dto.getRef());
dto.setStep(base_dto.getStep()+1);
dto.setDepth(base_dto.getDepth()+1);
int res = dao.reply(dto);
if (res > 0) {
response.sendRedirect("board_list?page="+nowPage);
}
}
}
- 게시판 상세보기 Servlet
package action;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import dao.BoardDAO;
import dto.BoardDTO;
@WebServlet("/view")
public class BoardViewAction extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 글이 깨지지 않게 인코딩 설정
request.setCharacterEncoding("utf-8");
// 페이지 설정 -- 조회수를 의도적으로 올리는 시도 방지
int nowPage = 1;
String page = request.getParameter("page");
if (page != null && !page.isEmpty()) {
nowPage = Integer.parseInt(page);
}
int idx = Integer.parseInt(request.getParameter("idx"));
// dao 객체 생성 -- DB에 여러 번 접근
BoardDAO dao = BoardDAO.getInstance();
// idx에 해당하는 게시글 조회
BoardDTO dto = dao.selectOne(idx);
// Session을 이용해서 새로고침 시 조회수 증가 방지
HttpSession session = request.getSession();
String show = (String)session.getAttribute("show");
if (show == null) { // session 최초 1회는 값이 없므으로 조회수 증가
// 조회수 증가
int res = dao.update_readhit(idx);
// show에 아무 값을 넣어서 session이 값을 가지도록 설정
// 값을 가진 동안에는 조회수 증가 방지
session.setAttribute("show", "0");
}
// 바인딩
request.setAttribute("dto", dto);
// 포워딩
RequestDispatcher disp = request.getRequestDispatcher("board_view.jsp?page="+nowPage);
disp.forward(request, response);
}
}
- 게시판 삭제 Servlet
package action;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.BoardDAO;
import dto.BoardDTO;
@WebServlet("/del")
public class BoardDeleteAction extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// del?idx=${dto.idx}
int idx = Integer.parseInt(request.getParameter("idx"));
// dao 객체 만들기 -- DB에 여러 번 접근을 위해
BoardDAO dao = BoardDAO.getInstance();
BoardDTO baseDTO = dao.selectOne(idx);
baseDTO.setSubject("삭제된 글 입니다");
baseDTO.setName("unknown");
int res = dao.del_update(baseDTO);
if (res > 0) {
response.getWriter().print("[{'param':'yes'}]");
} else {
response.getWriter().print("[{'param':'no'}]");
}
}
}
util 클래스
- 페이지 설정을 위한 클래스들
package util;
public class Common {
// 게시판용 정적 내부 클래스
// 외부 클래스에서 객체화 없이 바로 사용할 수 있다.
// 다만 내부 정적 클래스에서 외부 클래스의 필드 호출 시 static이 아니라면 사용할 수 없다.
public static class Board{
// 한 페이지에 보여줄 게시물 개수
public final static int BLOCKLIST = 10;
// 보여줄 페이지 메뉴의 수
public final static int BLOCKPAGE = 3;
}
}
package util;
public class Paging {
// 페이징 처리 메소드
public static String getPaging(String pageURL, int nowPage, int rowTotal, int blockList, int blockPage) {
int totalPage; // 전체 페이지 수
int startPage; // 시작 페이지 번호
int endPage; // 마지막 페이지 번호
boolean isPrevPage, isNextPage;
StringBuffer sb; // HTML 코드 작성
isPrevPage = isNextPage = false;
// 입력된 전제 자원을 통해 전체 페이지 수 계산
// 총 게시글 수 / 한 페이지에 보여줄 게시글 수
totalPage = (int)(rowTotal/blockList);
if (rowTotal % blockList != 0) totalPage++;
if (nowPage > totalPage) { // totalPage 초과해서 넘어가지 않게 설정
nowPage = totalPage;
}
// 시작 페이지와 마지막 페이지
// 게시판 하단에 보여줄 목록 숫자 --> 1 2 3 --> 4 5 6
startPage = (int)(((nowPage - 1) / blockPage) * blockPage + 1);
endPage = startPage + blockPage - 1;
// 마지막 페이지가 전체 페이지보다 작을 때, 시작 페이지가 1보다 클 때 이동버튼 작동 활성화
if (endPage < totalPage) isNextPage = true;
if (startPage > 1) isPrevPage = true;
// HTML 작성
sb = new StringBuffer();
if(isPrevPage) { // startPage >= 2
sb.append("<a href='"+pageURL+"?page="+(startPage-1)+"'>");
sb.append("◀");
sb.append("</a>");
} else {
sb.append("<span>◀</span>");
}
sb.append("");
for (int i = startPage; i <= endPage; i++) {
if (i > totalPage) break;
if (i == nowPage) { // 현재 페이지 강조
sb.append(" <b><font color='#ff0000'>");
sb.append(i);
sb.append("</font></b>");
} else {
sb.append(" <a href='"+pageURL+"?page="+i+"'>");
sb.append(i);
sb.append("</a>");
}
}
sb.append(" ");
if(isNextPage) { // endPage < totalPage
sb.append("<a href='"+pageURL+"?page="+(endPage+1)+"'>");
sb.append("▶");
sb.append("</a>");
} else {
sb.append("<span>▶</span>");
}
return sb.toString();
}
}
완성된 모습
-
ListAction Servlet에서 실행하면 게시판 사이트가 로딩된다. 미리 추가해둔 샘플 데이터들을 확인할 수 있다.
-
게시글을 누르면 상세하게 볼 수 있다.
-
댓글을 작성하고 등록하면 새로 추가된 댓글을 볼 수 있다. 그리고 첫 게시글을 한 번 접속했기 때문에 조회수도 1 증가했다.
-
댓글의 배치를 잘 보면 4번 글이 나중에 추가된 댓글인데 해당 댓글이 가장 위에 있다.
-
게시글을 작성할 때 제목, 이름, 내용, 비밀번호를 입력해야만 등록 가능하도록 설정한 것을 직접 확인할 수 있다.
-
필요한 내용을 모두 작성했다면 게시글이 성공적으로 등록된다. 게시글 중에선 가장 최근 추가된 글이기 때문에 번호가 5번이지만 가장 위에 있다.
-
댓글을 들어가 삭제 버튼을 누르면 비밀번호 일치를 확인한다. 일치하는 경우에만 정상적으로 삭제된다는 메시지가 뜬다.
-
삭제된 글은 제목이 "삭제된 글 입니다"로 바뀌고, 작성자도 "unknown"으로 바뀌며, 클릭할 수 없다.
-
조금 시간을 들여 이번엔 페이지 넘어가는 기능이 잘 들어갔는지 확인하기 위해 글을 30개 정도 추가 작성한다.
-
한 페이지에 게시글이 총 10개 보이며, 하단 메뉴바에서 다음 페이지로 넘어가는 버튼과 숫자들이 제대로 추가되었다.
-
해당 페이지 번호를 누르면 페이지가 정상적으로 로딩 된다.
-
다음 페이지 목록을 보기 위해 넘어가는 버튼을 누르면 4 페이지가 뜬다.
-
3 페이지에서 아무 글이나 선택해서 댓글을 작성해본다.
-
댓글 작성 후에도 3페이지에 남아 있는 것을 확인할 수 있다.